# !/usr/bin/env python3
# coding=utf8
"""
Python数据可视化：PyQt与ECharts的完美结合方案
https://baijiahao.baidu.com/s?id=1665933096817235451
"""
import dataclasses
import datetime
import json
import math
import numpy
import pathlib
import peewee
import playhouse.sqlite_ext
import sys
import PySide6
import PySide6.QtCore
import PySide6.QtWebEngineCore
import PySide6.QtWebEngineWidgets
import PySide6.QtWidgets
from PySide6 import QtWidgets
from typing import Any, Dict, List, Set, Tuple, Type, Optional, Union, Callable, Sequence
import vnpy.tools.database_cta
import vnpy.trader.utility
from vnpy.trader.constant import Exchange, Interval
from vnpy_ctabacktester.ui.widget import OptimizationSettingEditor


@dataclasses.dataclass(frozen=True)
class CtaBacktestUnit:
    """CTA回测单元"""

    strategy_name: str = ""
    exchange: Exchange = None
    symbol: str = ""
    interval: Interval = None
    start: datetime.datetime = None
    end: datetime.datetime = None


@dataclasses.dataclass(frozen=True)
class CtaBacktestUnitArea:
    """CTA回测单元的范围"""
    strategy_name: str = ""
    exchange: Exchange = None
    symbol: str = ""
    interval: Interval = None
    start: datetime.datetime = None
    end: datetime.datetime = None
    setting_area: Dict[str, list] = None


def output(msg) -> None:
    """"""
    print(f"{datetime.datetime.now()}\t{msg}")


class QDoubleSlider(PySide6.QtWidgets.QSlider):
    """"""
    # 基于QSlider实现DoubleSlider
    # https://blog.csdn.net/weixin_43435307/article/details/111830223

    valueChanged = PySide6.QtCore.Signal(float)

    def __init_subclass__(cls):
        """"""
        return super().__init_subclass__()

    def __init__(
        self,
        orientation: PySide6.QtCore.Qt.Orientation,
        parent: Optional[PySide6.QtWidgets.QWidget] = None,
    ) -> None:
        """"""
        super().__init__(orientation=orientation, parent=parent)

        # 以下写法属于无意间找到的, 现记录于此, 我个人不喜欢这种写法, 该写法参考了如下链接:
        # https://doc.qt.io/qtforpython-6/tutorials/basictutorial/signals_and_slots.html#specifying-signals-and-slots-by-method-signature-strings
        # PySide6.QtCore.QObject.connect(self, PySide6.QtCore.SIGNAL("valueChanged(int)"), self, PySide6.QtCore.SLOT("valueIntToDouble(int)"))
        super().valueChanged.connect(self.valueIntToDouble)

        self.__min_value: float = super().minimum()
        self.__max_value: float = super().maximum()
        self.__once_step: int = super().singleStep()
        self.__sync_data()

    def setMaximum(self, arg__1: float) -> None:
        """"""
        self.__max_value = arg__1
        self.__sync_data()

    def setMinimum(self, arg__1: float) -> None:
        """"""
        self.__min_value = arg__1
        self.__sync_data()

    def setRange(self, _min: float, _max: float) -> None:
        """"""
        self.__min_value = _min
        self.__max_value = _max
        self.__sync_data()

    def setSingleStep(self, val: float) -> None:
        """"""
        self.__once_step = val
        self.__sync_data()

    def setValue(self, arg__1: float) -> None:
        """"""
        PySide6.QtWidgets.QSlider.setValue(
            self,
            round(pow(10, self.__precision) * arg__1, ndigits=9)
        )

    @PySide6.QtCore.Slot(int)
    def valueIntToDouble(self, value: int):
        """"""
        val: float = round(pow(10, self.__precision * -1) * value, ndigits=9)
        self.valueChanged.emit(val)

    @classmethod
    def __float_ndigit(cls, val) -> int:
        """"""
        return len(numpy.format_float_positional(val, precision=9).split(sep='.')[1])

    def __sync_data(self) -> None:
        """"""
        self.__precision: int = max(
            self.__float_ndigit(self.__min_value),
            self.__float_ndigit(self.__max_value),
            self.__float_ndigit(self.__once_step),
        )
        PySide6.QtWidgets.QSlider.setRange(
            self,
            round(pow(10, self.__precision) * self.__min_value, ndigits=9),
            round(pow(10, self.__precision) * self.__max_value, ndigits=9)
        )
        PySide6.QtWidgets.QSlider.setSingleStep(
            self,
            round(pow(10, self.__precision) * self.__once_step, ndigits=9)
        )

    @classmethod
    def _test(cls) -> None:
        """"""
        qapp = QtWidgets.QApplication.instance()
        if qapp is None:
            qapp: QtWidgets.QApplication = QtWidgets.QApplication(sys.argv)

        is_custom: bool = True

        slider = QDoubleSlider(PySide6.QtCore.Qt.Horizontal) if is_custom else PySide6.QtWidgets.QSlider(PySide6.QtCore.Qt.Horizontal)
        print("current: slider,type      :", type(slider))
        print("default: slider.minimum   :", slider.minimum())
        print("default: slider.maximum   :", slider.maximum())
        print("default: slider.singleStep:", slider.singleStep())
        slider.setRange(0.5, 1.5)
        slider.setSingleStep(0.1)
        slider.valueChanged.connect(lambda _: print(_))
        slider.show()

        qapp.exec()


class UnitListWidget(PySide6.QtWidgets.QWidget):
    """"""
    setArgs = PySide6.QtCore.Signal()  # 定义一个无参的 signal

    def __init__(
        self,
        parent: Optional[PySide6.QtWidgets.QWidget] = None,
        f: PySide6.QtCore.Qt.WindowFlags = PySide6.QtCore.Qt.WindowFlags(),  # Default(PySide6.QtCore.Qt.WindowFlags)
    ) -> None:
        """
        如果 __init__ 写成
            def __init__(self, parent=None, f: PySide6.QtCore.Qt.WindowFlags = ...) -> None: super().__init__(parent, f)
        那么会报错:
        File "./show.py", line 61, in __init__
            def __init__(self, parent=None, f: PySide6.QtCore.Qt.WindowFlags = ...) -> None: super().__init__(parent, f)
        TypeError: 'PySide6.QtWidgets.QWidget.__init__' called with wrong argument types:
          PySide6.QtWidgets.QWidget.__init__(MainWindow, ellipsis)
        Supported signatures:
          PySide6.QtWidgets.QWidget.__init__(Optional[PySide6.QtWidgets.QWidget] = None, PySide6.QtCore.Qt.WindowFlags = Default(Qt.WindowFlags))
        但是使用 Default(Qt.WindowFlags) 时又会报错 name 'Default' is not defined 所以没法直接使用,
        直到我看到了 https://doc.qt.io/qtforpython-6.2/PySide6/QtWidgets/QInputDialog.html 里面的一个写法:
            class PySide6.QtWidgets.QInputDialog([parent=None[, flags=Qt.WindowFlags()]])
        我才意识到 Default(PySide6.QtCore.Qt.WindowFlags) 可以写成 PySide6.QtCore.Qt.WindowFlags() 的样子,
        """
        super().__init__(parent, f)

        self.unit_s: List[CtaBacktestUnit] = []

        self.init_ui()
        self.init_data() if 0 else None

    def init_ui(self) -> None:
        """"""
        self.setMaximumWidth(400)

        self.combo_strategy_name: QtWidgets.QComboBox = QtWidgets.QComboBox()
        self.combo_strategy_name.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
        self.check_strategy_name: QtWidgets.QCheckBox = QtWidgets.QCheckBox()
        self.check_strategy_name.clicked.connect(self.checkBox_clicked)

        self.combo_exchange: QtWidgets.QComboBox = QtWidgets.QComboBox()
        self.check_exchange: QtWidgets.QCheckBox = QtWidgets.QCheckBox()
        self.check_exchange.clicked.connect(self.checkBox_clicked)

        self.combo_symbol: QtWidgets.QComboBox = QtWidgets.QComboBox()
        self.check_symbol: QtWidgets.QCheckBox = QtWidgets.QCheckBox()
        self.check_symbol.clicked.connect(self.checkBox_clicked)

        self.combo_interval: QtWidgets.QComboBox = QtWidgets.QComboBox()
        self.check_interval: QtWidgets.QCheckBox = QtWidgets.QCheckBox()
        self.check_interval.clicked.connect(self.checkBox_clicked)

        self.combo_start: QtWidgets.QComboBox = QtWidgets.QComboBox()
        self.check_start: QtWidgets.QCheckBox = QtWidgets.QCheckBox()
        self.check_start.clicked.connect(self.checkBox_clicked)

        self.combo_end: QtWidgets.QComboBox = QtWidgets.QComboBox()
        self.check_end: QtWidgets.QCheckBox = QtWidgets.QCheckBox()
        self.check_end.clicked.connect(self.checkBox_clicked)

        mainLayout: QtWidgets.QVBoxLayout = QtWidgets.QVBoxLayout(self)  # 垂直布局

        pathLayout: QtWidgets.QHBoxLayout = QtWidgets.QHBoxLayout()

        self.edit_filepath: QtWidgets.QLineEdit = QtWidgets.QLineEdit()
        self.edit_filepath.setText(str(vnpy.trader.utility.get_file_path("database.db")))
        pathLayout.addWidget(self.edit_filepath)

        button_select: QtWidgets.QPushButton = QtWidgets.QPushButton("选择文件")
        button_select.clicked.connect(self.select_file)
        pathLayout.addWidget(button_select)

        mainLayout.addLayout(pathLayout)

        horizontal_line_1: QtWidgets.QFrame = QtWidgets.QFrame()  # 水平分隔线
        horizontal_line_1.setFrameShape(QtWidgets.QFrame.HLine)
        horizontal_line_1.setFrameShadow(QtWidgets.QFrame.Plain)

        mainLayout.addWidget(horizontal_line_1)

        grid: QtWidgets.QGridLayout = QtWidgets.QGridLayout()
        row: int = 0
        grid.addWidget(QtWidgets.QLabel("策略名称(strategy_name)"), row, 0)
        grid.addWidget(self.combo_strategy_name, row, 1)
        grid.addWidget(self.check_strategy_name, row, 2)
        row += 1
        grid.addWidget(QtWidgets.QLabel("交易所(exchange)"), row, 0)
        grid.addWidget(self.combo_exchange, row, 1)
        grid.addWidget(self.check_exchange, row, 2)
        row += 1
        grid.addWidget(QtWidgets.QLabel("合约代码(symbol)"), row, 0)
        grid.addWidget(self.combo_symbol, row, 1)
        grid.addWidget(self.check_symbol, row, 2)
        row += 1
        grid.addWidget(QtWidgets.QLabel("Ｋ线周期(interval)"), row, 0)
        grid.addWidget(self.combo_interval, row, 1)
        grid.addWidget(self.check_interval, row, 2)
        row += 1
        grid.addWidget(QtWidgets.QLabel("开始日期(start)"), row, 0)
        grid.addWidget(self.combo_start, row, 1)
        grid.addWidget(self.check_start, row, 2)
        row += 1
        grid.addWidget(QtWidgets.QLabel("结束日期(end)"), row, 0)
        grid.addWidget(self.combo_end, row, 1)
        grid.addWidget(self.check_end, row, 2)

        mainLayout.addLayout(grid)

        hboxLayout: QtWidgets.QHBoxLayout = QtWidgets.QHBoxLayout()

        button_reload: QtWidgets.QPushButton = QtWidgets.QPushButton("重新载入")
        button_reload.clicked.connect(self.init_data)
        hboxLayout.addWidget(button_reload)

        button_setArg: QtWidgets.QPushButton = QtWidgets.QPushButton("设置参数")
        button_setArg.clicked.connect(lambda: self.setArgs.emit())  # 定义一个lambda, 这个lambda的作用是发射信号,
        hboxLayout.addWidget(button_setArg)

        mainLayout.addLayout(hboxLayout)

    def init_data(self) -> None:
        """"""
        XXX.reinitialize(sqlite_filepath=self.edit_filepath.text())

        output(f"载入数据, 开始,")
        dttm_beg: datetime.datetime = datetime.datetime.now()
        self.unit_s: List[CtaBacktestUnit] = XXX.query_unit_list()
        dttm_end: datetime.datetime = datetime.datetime.now()
        output(f"载入数据, 结束, 耗时{dttm_end - dttm_beg}")

        self.set_QComboBox(self.unit_s, to_unchecked=True)

    def select_file(self) -> None:
        """"""
        result: tuple[str, str] = QtWidgets.QFileDialog.getOpenFileName(
            parent=self,
            caption="选择 SQLite 文件",
            dir=str(vnpy.trader.utility.get_file_path("")),
            filter="SQLite 文件 (*.db *.sdb *.sqlite *.db3 *.s3db *.sqlite3 *.sl3);;所有文件 (*)",
        )
        if result[0]:
            self.edit_filepath.setText(result[0])

    def checkBox_clicked(self) -> None:
        """"""
        mapping: dict = self.get_Checked_mapping()
        unit_s: List[CtaBacktestUnit] = self.filter_UnitList(self.unit_s, mapping)
        self.set_QComboBox(unit_s, to_unchecked=False)

    def get_Checked_mapping(self) -> dict:
        """根据 下拉框+复选框 提取选定的那些个参数"""
        mapping: dict = {}

        if self.check_strategy_name.isChecked() and 0 < self.combo_strategy_name.count():
            mapping["strategy_name"] = self.combo_strategy_name.currentText()

        if self.check_exchange.isChecked() and 0 < self.combo_exchange.count():
            mapping["exchange"] = Exchange(self.combo_exchange.currentText())

        if self.check_symbol.isChecked() and 0 < self.combo_symbol.count():
            mapping["symbol"] = self.combo_symbol.currentText()

        if self.check_interval.isChecked() and 0 < self.combo_interval.count():
            mapping["interval"] = Interval(self.combo_interval.currentText())

        if self.check_start.isChecked() and 0 < self.combo_start.count():
            mapping["start"] = datetime.datetime.strptime(self.combo_start.currentText(), "%Y-%m-%d %H:%M:%S")

        if self.check_end.isChecked() and 0 < self.combo_end.count():
            mapping["end"] = datetime.datetime.strptime(self.combo_end.currentText(), "%Y-%m-%d %H:%M:%S")

        return mapping

    @classmethod
    def filter_UnitList(cls, unit_s: List[CtaBacktestUnit], mapping: dict) -> List[CtaBacktestUnit]:
        """"""
        for key, val in mapping.items() if mapping else {}:
            unit_s = [_ for _ in unit_s if getattr(_, key) == val]

        return unit_s

    def set_QComboBox(self, unit_s: List[CtaBacktestUnit], to_unchecked: bool) -> None:
        """"""
        if to_unchecked:
            self.check_strategy_name.setChecked(False)
            self.check_exchange.setChecked(False)
            self.check_symbol.setChecked(False)
            self.check_interval.setChecked(False)
            self.check_start.setChecked(False)
            self.check_end.setChecked(False)
            self.check_end.setChecked(False)

        if self.check_strategy_name.isChecked():
            self.combo_strategy_name.setEnabled(False)
        else:
            strategy_name_s: List[str] = sorted(set([_.strategy_name for _ in unit_s]))
            self.combo_strategy_name.clear()
            self.combo_strategy_name.addItems(strategy_name_s)
            self.combo_strategy_name.setEnabled(True)

        if self.check_exchange.isChecked():
            self.combo_exchange.setEnabled(False)
        else:
            exchange_s: List[str] = sorted(set([_.exchange.value for _ in unit_s]))
            self.combo_exchange.clear()
            self.combo_exchange.addItems(exchange_s)
            self.combo_exchange.setEnabled(True)

        if self.check_symbol.isChecked():
            self.combo_symbol.setEnabled(False)
        else:
            symbol_s: List[str] = sorted(set([_.symbol for _ in unit_s]))
            self.combo_symbol.clear()
            self.combo_symbol.addItems(symbol_s)
            self.combo_symbol.setEnabled(True)

        if self.check_interval.isChecked():
            self.combo_interval.setEnabled(False)
        else:
            interval_s: List[str] = sorted(set([_.interval.value for _ in unit_s]))
            self.combo_interval.clear()
            self.combo_interval.addItems(interval_s)
            self.combo_interval.setEnabled(True)

        if self.check_start.isChecked():
            self.combo_start.setEnabled(False)
        else:
            start_s: List[str] = sorted(set([_.start.strftime("%Y-%m-%d %H:%M:%S") for _ in unit_s]))
            self.combo_start.clear()
            self.combo_start.addItems(start_s)
            self.combo_start.setEnabled(True)

        if self.check_end.isChecked():
            self.combo_end.setEnabled(False)
        else:
            end_s: List[str] = sorted(set([_.end.strftime("%Y-%m-%d %H:%M:%S") for _ in unit_s]))
            self.combo_end.clear()
            self.combo_end.addItems(end_s)
            self.combo_end.setEnabled(True)

    def get_cta_backtest_unit(self) -> CtaBacktestUnit:
        """"""
        # 使用之前, 一般会使用函数 simple_check_ok 进行前置检查,
        unit: CtaBacktestUnit = CtaBacktestUnit(
            strategy_name=self.combo_strategy_name.currentText(),
            exchange=Exchange(self.combo_exchange.currentText()),
            symbol=self.combo_symbol.currentText(),
            interval=Interval(self.combo_interval.currentText()),
            start=datetime.datetime.strptime(self.combo_start.currentText(), "%Y-%m-%d %H:%M:%S"),
            end=datetime.datetime.strptime(self.combo_end.currentText(), "%Y-%m-%d %H:%M:%S"),
        )
        return unit

    def simple_check_ok(self) -> bool:
        """"""
        is_ok_1: bool = (
            True and
            self.combo_strategy_name.count() and
            self.combo_exchange.count() and
            self.combo_symbol.count() and
            self.combo_interval.count() and
            self.combo_start.count() and
            self.combo_end.count()
        )
        is_ok_2: bool = (
            True
            and not self.combo_strategy_name.isEnabled()
            and not self.combo_exchange.isEnabled()
            and not self.combo_symbol.isEnabled()
            and not self.combo_interval.isEnabled()
            and not self.combo_start.isEnabled()
            and not self.combo_end.isEnabled()
        )
        return is_ok_1 and is_ok_2


class UnitAreaWidget(PySide6.QtWidgets.QWidget):
    """"""
    drawIt = PySide6.QtCore.Signal()

    TYPE_line: str = "line"
    TYPE_bar3D: str = "bar3D"
    TYPE_scatter3D: str = "scatter3D"

    @dataclasses.dataclass
    class ParameterData(object):
        """"""
        name: str = ""
        items: list = dataclasses.field(default_factory=list)  # 这个参数的所有可能取值(combo_box 的所有取值)
        label: QtWidgets.QLabel = None
        combo_box: QtWidgets.QComboBox = None
        spin_box: QtWidgets.QDoubleSpinBox = None
        slider: QDoubleSlider = None

    def __init__(self, parent: Optional[PySide6.QtWidgets.QWidget] = None) -> None:
        """"""
        super().__init__(parent)

        self.unit: CtaBacktestUnit = None

        self.parameterdata_s: Dict[str, UnitAreaWidget.ParameterData] = {}
        self.grid_layout: QtWidgets.QGridLayout = QtWidgets.QGridLayout()
        self.combo_x_axis: QtWidgets.QComboBox = QtWidgets.QComboBox()  # x轴
        self.combo_y_axis: QtWidgets.QComboBox = QtWidgets.QComboBox()  # y轴
        self.combo_z_axis: QtWidgets.QComboBox = QtWidgets.QComboBox()  # z轴
        self.combo_a_axis: QtWidgets.QComboBox = QtWidgets.QComboBox()  # 结果轴
        self.combo_t_name: QtWidgets.QComboBox = QtWidgets.QComboBox()  # ECharts图形类型名

        self.init_ui()

    def init_ui(self) -> None:
        """"""
        self.setMaximumWidth(400)

        mainLayout: QtWidgets.QVBoxLayout = QtWidgets.QVBoxLayout(self)  # 垂直布局

        self.init_QGridLayout()

        mainLayout.addLayout(self.grid_layout)

        self.combo_x_axis.clear()
        self.combo_x_axis.addItems(sorted(self.parameterdata_s.keys()))
        self.combo_x_axis.currentTextChanged.connect(self.combo_xyz_axis_currentTextChanged)

        self.combo_y_axis.clear()
        self.combo_y_axis.addItems(sorted(self.parameterdata_s.keys()))
        self.combo_y_axis.currentTextChanged.connect(self.combo_xyz_axis_currentTextChanged)

        self.combo_z_axis.clear()
        self.combo_z_axis.addItems(sorted(self.parameterdata_s.keys()))
        self.combo_z_axis.currentTextChanged.connect(self.combo_xyz_axis_currentTextChanged)

        items: List[str] = sorted(OptimizationSettingEditor.DISPLAY_NAME_MAP.values())
        self.combo_a_axis.addItems(sorted(items))
        self.combo_a_axis.setCurrentText("total_return")

        self.combo_t_name.addItems([self.TYPE_line, self.TYPE_bar3D, self.TYPE_scatter3D])
        self.combo_t_name.setCurrentText(self.TYPE_scatter3D)
        self.combo_t_name.currentTextChanged.connect(self.combo_t_name_currentTextChanged)

        self.check_modify = QtWidgets.QCheckBox()
        self.check_modify.setText("绘图时修改负值")
        push_button_paint: QtWidgets.QPushButton = QtWidgets.QPushButton("绘图")
        push_button_paint.clicked.connect(lambda: self.drawIt.emit())
        push_button_paint.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
        hboxLayout: QtWidgets.QHBoxLayout = QtWidgets.QHBoxLayout()
        hboxLayout.addWidget(self.check_modify)
        hboxLayout.addWidget(push_button_paint)

        gridLayout: QtWidgets.QGridLayout = QtWidgets.QGridLayout()
        row: int = 0
        gridLayout.addWidget(QtWidgets.QLabel("x轴"), row, 0)
        gridLayout.addWidget(self.combo_x_axis, row, 1, 1, 4)
        row += 1
        gridLayout.addWidget(QtWidgets.QLabel("y轴"), row, 0)
        gridLayout.addWidget(self.combo_y_axis, row, 1, 1, 4)
        row += 1
        gridLayout.addWidget(QtWidgets.QLabel("z轴"), row, 0)
        gridLayout.addWidget(self.combo_z_axis, row, 1, 1, 4)
        row += 1
        gridLayout.addWidget(QtWidgets.QLabel("a值"), row, 0)
        gridLayout.addWidget(self.combo_a_axis, row, 1, 1, 4)
        row += 1
        gridLayout.addWidget(self.combo_t_name, row, 0)
        gridLayout.addLayout(hboxLayout, row, 1, 1, 4)

        mainLayout.addLayout(gridLayout)

    def init_QGridLayout(self) -> None:
        """"""
        for parameterdata in [*self.parameterdata_s.values()]:
            parameterdata: UnitAreaWidget.ParameterData = parameterdata

            parameterdata.label.setParent(None)
            parameterdata.combo_box.setParent(None)
            parameterdata.spin_box.setParent(None)
            parameterdata.slider.setParent(None)

            self.grid_layout.removeWidget(parameterdata.label)
            self.grid_layout.removeWidget(parameterdata.combo_box)
            self.grid_layout.removeWidget(parameterdata.spin_box)
            self.grid_layout.removeWidget(parameterdata.slider)

            PySide6.QtCore.QObject.disconnect(parameterdata.combo_box, None, None, None)  # 解绑所有
            PySide6.QtCore.QObject.disconnect(parameterdata.spin_box, None, None, None)
            PySide6.QtCore.QObject.disconnect(parameterdata.slider, None, None, None)

            del parameterdata.label
            del parameterdata.combo_box
            del parameterdata.spin_box
            del parameterdata.slider

            del self.parameterdata_s[parameterdata.name]

        assert len(self.parameterdata_s) == 0

        if self.unit is None:
            setting_area: Dict[str, list] = {}
        else:
            unit_area: CtaBacktestUnitArea = XXX.query_unit_area(unit=self.unit)
            setting_area: Dict[str, list] = unit_area.setting_area

        for idx, parameter in enumerate(sorted(setting_area.keys())):
            values: list = setting_area[parameter]

            parameterdata: UnitAreaWidget.ParameterData = UnitAreaWidget.ParameterData(
                name=parameter,
                items=values,
                label=QtWidgets.QLabel(parameter),
                combo_box=QtWidgets.QComboBox(),
                spin_box=QtWidgets.QDoubleSpinBox(),
                slider=QDoubleSlider(PySide6.QtCore.Qt.Orientation.Horizontal),
            )
            self.parameterdata_s[parameterdata.name] = parameterdata

            parameterdata.combo_box.addItems([str(item) for item in parameterdata.items])
            parameterdata.combo_box.currentTextChanged.connect(self.comboBox_currentTextChanged)

            assert all(isinstance(_, type(values[0])) for _ in values)  # values里面的数据的类型都一样,

            if isinstance(values[0], str):  # 特殊处理str类型,
                parameterdata.spin_box.setEnabled(False)
                parameterdata.slider.setEnabled(False)
                PySide6.QtCore.QObject.disconnect(parameterdata.combo_box, None, None, None)
                PySide6.QtCore.QObject.disconnect(parameterdata.spin_box, None, None, None)
                PySide6.QtCore.QObject.disconnect(parameterdata.slider, None, None, None)
            else:
                parameterdata.spin_box.setRange(min(values), max(values))
                parameterdata.spin_box.setSingleStep(min(numpy.diff(sorted(values))))
                parameterdata.spin_box.valueChanged.connect(self.spinBox_valueChanged)

                parameterdata.slider.setRange(min(values), max(values))
                parameterdata.slider.setSingleStep(min(numpy.diff(sorted(values))))
                parameterdata.slider.valueChanged.connect(self.slider_valueChanged)

            self.grid_layout.addWidget(parameterdata.label, idx, 0)
            self.grid_layout.addWidget(parameterdata.combo_box, idx, 1)
            self.grid_layout.addWidget(parameterdata.spin_box, idx, 2)
            self.grid_layout.addWidget(parameterdata.slider, idx, 3)

        self.combo_t_name.setCurrentText(self.TYPE_scatter3D)

        self.combo_x_axis.clear()
        self.combo_x_axis.addItems(sorted(self.parameterdata_s.keys()))

        self.combo_y_axis.clear()
        self.combo_y_axis.addItems(sorted(self.parameterdata_s.keys()))

        self.combo_z_axis.clear()
        self.combo_z_axis.addItems(sorted(self.parameterdata_s.keys()))

    def reload_data(self) -> None:
        """"""
        sender: PySide6.QtCore.QObject = self.sender()
        assert isinstance(sender, UnitListWidget)
        if not sender.simple_check_ok():
            return

        self.unit: CtaBacktestUnit = sender.get_cta_backtest_unit()

        output(f"设置参数, 开始,")
        dttm_beg: datetime.datetime = datetime.datetime.now()
        self.init_QGridLayout()
        dttm_end: datetime.datetime = datetime.datetime.now()
        output(f"设置参数, 结束, 耗时{dttm_end - dttm_beg}")

    def combo_t_name_currentTextChanged(self, text: str) -> None:
        """"""
        sender: PySide6.QtCore.QObject = self.sender()
        assert isinstance(sender, QtWidgets.QComboBox)
        assert self.combo_t_name == sender

        if False:
            pass
        elif text == self.TYPE_line:
            self.combo_y_axis.setEnabled(False)
            self.combo_z_axis.setEnabled(False)
            self.combo_y_axis.setCurrentText(self.combo_x_axis.currentText())
            self.combo_z_axis.setCurrentText(self.combo_y_axis.currentText())
        elif text == self.TYPE_bar3D:
            self.combo_y_axis.setEnabled(True)
            self.combo_z_axis.setEnabled(False)
            self.combo_z_axis.setCurrentText(self.combo_y_axis.currentText())
        elif text == self.TYPE_scatter3D:
            self.combo_y_axis.setEnabled(True)
            self.combo_z_axis.setEnabled(True)
        else:
            assert text in [self.TYPE_line, self.TYPE_bar3D, self.TYPE_scatter3D]

    def comboBox_currentTextChanged(self, text: str) -> None:
        """"""
        # QComboBox => QSpinBox => QComboBox
        #           => QSlider  => QComboBox
        sender: PySide6.QtCore.QObject = self.sender()
        assert isinstance(sender, QtWidgets.QComboBox)

        cur_parameterdata: UnitAreaWidget.ParameterData = None

        for parameterdata in self.parameterdata_s.values():
            if sender == parameterdata.combo_box:
                cur_parameterdata = parameterdata
                break
        assert cur_parameterdata is not None

        cur_parameterdata.combo_box.setProperty('', text)

        value: float = float(text)

        cur_parameterdata.spin_box.setValue(value)
        cur_parameterdata.slider.setValue(value)

    def spinBox_valueChanged(self, value: float) -> None:
        """"""
        # QSpinBox => QComboBox => QSpinBox
        #                       => QSlider  => QComboBox
        sender: PySide6.QtCore.QObject = self.sender()
        assert isinstance(sender, (QtWidgets.QSpinBox, QtWidgets.QDoubleSpinBox))

        cur_parameterdata: UnitAreaWidget.ParameterData = None

        for parameterdata in self.parameterdata_s.values():
            if sender == parameterdata.spin_box:
                cur_parameterdata = parameterdata
                break
        assert cur_parameterdata is not None

        self.smart_modify_QComboBox(cur_parameterdata, value)

    def slider_valueChanged(self, value: float) -> None:
        """"""
        # QSlider => QComboBox => QSpinBox => QComboBox
        #                      => QSlider
        sender: PySide6.QtCore.QObject = self.sender()
        assert isinstance(sender, (QtWidgets.QSlider, QDoubleSlider))

        cur_parameterdata: UnitAreaWidget.ParameterData = None

        for parameterdata in self.parameterdata_s.values():
            if sender == parameterdata.slider:
                cur_parameterdata = parameterdata
                break
        assert cur_parameterdata is not None

        self.smart_modify_QComboBox(cur_parameterdata, value)

    @classmethod
    def smart_modify_QComboBox(cls, parameterdata: ParameterData, new_value: float) -> None:
        """"""
        # 因为 QSpinBox/QSlider 里的数据都是连续的, QComboBox 里的数据不连续, 所以用这个函数修正,
        eq_items: list = [item for item in parameterdata.items if math.isclose(item, new_value)]
        assert len(eq_items) <= 1  # 如果存在数据, 逻辑上只能存在一个,

        if eq_items:
            new_item = eq_items[0]
            parameterdata.combo_box.setCurrentText(str(new_item))
            # combo_box 的 property 里的数据由 combo_box 自己发出的 signal 绑定的函数进行修改,
        else:
            old_text: str = parameterdata.combo_box.property('')
            old_value: float = float(old_text if old_text else -sys.maxsize)

            if new_value < old_value:  # 想调小数据
                lt_items: list = [item for item in parameterdata.items if item < new_value]
                if lt_items:
                    new_item = lt_items[-1]
                    parameterdata.combo_box.setCurrentText(str(new_item))

            if new_value > old_value:  # 想调大数据
                gt_items: list = [item for item in parameterdata.items if item > new_value]
                if gt_items:
                    new_item = gt_items[0]
                    parameterdata.combo_box.setCurrentText(str(new_item))

    def combo_xyz_axis_currentTextChanged(self, text: str) -> None:
        """"""
        sender: PySide6.QtCore.QObject = self.sender()
        assert isinstance(sender, QtWidgets.QComboBox)

        if self.combo_x_axis.isEnabled() and not self.combo_y_axis.isEnabled() and not self.combo_z_axis.isEnabled():
            if sender != self.combo_x_axis:
                pass
            self.combo_y_axis.setCurrentText(self.combo_x_axis.currentText())
            self.combo_z_axis.setCurrentText(self.combo_y_axis.currentText())
        elif self.combo_x_axis.isEnabled() and self.combo_y_axis.isEnabled() and not self.combo_z_axis.isEnabled():
            if sender == self.combo_z_axis:
                pass
            self.combo_z_axis.setCurrentText(self.combo_y_axis.currentText())
        elif self.combo_x_axis.isEnabled() and self.combo_y_axis.isEnabled() and self.combo_z_axis.isEnabled():
            pass
        else:
            assert False

        name_x: str = text if (sender == self.combo_x_axis) else self.combo_x_axis.currentText()
        name_y: str = text if (sender == self.combo_y_axis) else self.combo_y_axis.currentText()
        name_z: str = text if (sender == self.combo_z_axis) else self.combo_z_axis.currentText()

        if self.combo_x_axis.isEnabled() and not self.combo_y_axis.isEnabled() and not self.combo_z_axis.isEnabled():
            assert name_x == name_y == name_z
        elif self.combo_x_axis.isEnabled() and self.combo_y_axis.isEnabled() and not self.combo_z_axis.isEnabled():
            assert name_y == name_z
        elif self.combo_x_axis.isEnabled() and self.combo_y_axis.isEnabled() and self.combo_z_axis.isEnabled():
            pass
        else:
            assert False

        for parameterdata in self.parameterdata_s.values():
            parameterdata: UnitAreaWidget.ParameterData = parameterdata

            if parameterdata.name in (name_x, name_y, name_z):
                parameterdata.label.setStyleSheet("QLabel{color:rgb(220,20,60);}")
            else:
                parameterdata.label.setStyleSheet("")

    def get_name_for_draw(self) -> str:
        """绘图的名字"""
        return self.combo_t_name.currentText()

    def get_data_for_draw(self) -> List[list]:
        """绘图的数据"""
        assert 0 < self.combo_x_axis.count()
        assert 0 < self.combo_y_axis.count()
        assert 0 < self.combo_z_axis.count()
        assert 0 < self.combo_a_axis.count()

        name_x: str = self.combo_x_axis.currentText()
        name_y: str = self.combo_y_axis.currentText()
        name_z: str = self.combo_z_axis.currentText()
        name_a: str = self.combo_a_axis.currentText()

        # 有一个立方体的烤箱, (x,y,z)指定了立方体里的一个点, a 表示这个点的温度,
        # 有一个平面的地皮, (x,y)指定了平面上的一个位置, a 表示这个位置上的楼房的高度,
        # 如果我想画楼房的高度, 那么 name_z 就无效了, 此时应当将其选定成 name_x/name_y 的值, 方便程序统一逻辑,
        name_s: set = set([name_x, name_y, name_z])  # 因为x和y和z可能同值, 所以用set去重

        # 页面展示了所有的参数, 每个参数都能选值, 选值结束之后, 这段逻辑用于提取所有参数的值,
        setting_filter: dict = {}
        for name, parameterdata in self.parameterdata_s.items():
            assert name == parameterdata.name
            assert len(parameterdata.items) == parameterdata.combo_box.count()

            item = parameterdata.items[parameterdata.combo_box.currentIndex()]
            assert str(item) == parameterdata.combo_box.currentText()

            setting_filter[parameterdata.name] = item

        # 因为要画出来 x/y/z 的 a 的图像, 所以要删掉 x/y/z 的参数,
        # 因为不根据这个参数进行过滤, 就能拿到这个参数的所有数据,
        for name in name_s:
            del setting_filter[name]

        backtest_data_s: List[vnpy.tools.database_cta.CtaStatisticsData] = XXX.query_data_list(unit=self.unit, setting_filter=setting_filter)
        if True:
            # 疑似echarts最多展示200*200=4W数据, 参数一般是int从1开始递增, 所以这里粗暴地丢弃200以上的数据,
            backtest_data_s = [_ for _ in backtest_data_s if max(x for x in _.setting.values() if not isinstance(x, str)) <= 201]  # TODO: 200_V,202_X

        if True:
            if backtest_data_s:
                # setting.keys == setting_filter.keys + name_s
                assert sorted(backtest_data_s[0].setting.keys()) == sorted([*setting_filter.keys()] + [*name_s])

        if self.check_modify.isChecked() and backtest_data_s:
            # 看绝对值, 如果负数远大于正数时, 画出的图形里的正值就会被缩小到微不可查,
            # 为了能看到正值的图形, 就需要截断负值,
            max_a = max([_.result[name_a] for _ in backtest_data_s])
            min_a = min([_.result[name_a] for _ in backtest_data_s])
            if min_a < 0 and 0 < max_a and abs(max_a) < abs(min_a):
                for _ in backtest_data_s:
                    _.result[name_a] = max(-abs(max_a), _.result[name_a])

        xyza_s: List[list] = [[btd.setting[name_x], btd.setting[name_y], btd.setting[name_z], btd.result[name_a]] for btd in backtest_data_s]  # noqa
        xyza_s.insert(0, [name_x, name_y, name_z, name_a])

        return xyza_s

    def simple_check_ok(self) -> None:
        """"""
        is_ok: bool = \
            self.combo_x_axis.count() and \
            self.combo_y_axis.count() and \
            self.combo_z_axis.count() and \
            self.combo_a_axis.count()
        return is_ok


class XXX(object):
    """"""

    @classmethod
    def reinitialize(cls, sqlite_filepath: Optional[str] = None):
        """重新初始化SQLite文件"""
        # 添加用户定义的运算符
        # https://www.osgeo.cn/peewee/peewee/query_operators.html#adding-user-defined-operators
        #
        if sqlite_filepath:
            # Peewee 如何在Peewee中动态设置SQLite数据库文件
            # https://geek-docs.com/peewee/peewee-questions/12_peewee_how_can_i_dynamically_set_the_sqlite_database_file_in_peewee.html
            db = vnpy.tools.database_cta.DbCtaStatisticsData._meta.database
            assert isinstance(db, playhouse.sqlite_ext.SqliteExtDatabase)
            if pathlib.Path(vnpy.tools.database_cta.DbCtaStatisticsData._meta.database.database) != pathlib.Path(sqlite_filepath):
                db.init(database=sqlite_filepath)

    @classmethod
    def query_unit_list(cls) -> List[CtaBacktestUnit]:
        """总共有哪些个单元"""
        model_select: peewee.ModelSelect = (
            vnpy.tools.database_cta.DbCtaStatisticsData.select(
                vnpy.tools.database_cta.DbCtaStatisticsData.strategy_name,
                vnpy.tools.database_cta.DbCtaStatisticsData.exchange,
                vnpy.tools.database_cta.DbCtaStatisticsData.symbol,
                vnpy.tools.database_cta.DbCtaStatisticsData.interval,
                vnpy.tools.database_cta.DbCtaStatisticsData.start,
                vnpy.tools.database_cta.DbCtaStatisticsData.end,
                peewee.fn.COUNT(vnpy.tools.database_cta.DbCtaStatisticsData.id).alias("cnt"),
            )
            .group_by(
                vnpy.tools.database_cta.DbCtaStatisticsData.strategy_name,
                vnpy.tools.database_cta.DbCtaStatisticsData.exchange,
                vnpy.tools.database_cta.DbCtaStatisticsData.symbol,
                vnpy.tools.database_cta.DbCtaStatisticsData.interval,
                vnpy.tools.database_cta.DbCtaStatisticsData.start,
                vnpy.tools.database_cta.DbCtaStatisticsData.end,
            )
        )

        unit_s: List[CtaBacktestUnit] = []

        for db_line in model_select:
            db_line: vnpy.tools.database_cta.DbCtaStatisticsData = db_line
            unit: CtaBacktestUnit = CtaBacktestUnit(
                strategy_name=db_line.strategy_name,
                exchange=Exchange(db_line.exchange),
                symbol=db_line.symbol,
                interval=Interval(db_line.interval),
                start=datetime.datetime.fromtimestamp(db_line.start.timestamp(), tz=None),
                end=datetime.datetime.fromtimestamp(db_line.end.timestamp(), tz=None),
            )
            unit_s.append(unit)

        return unit_s

    @classmethod
    def query_unit_area(cls, unit: CtaBacktestUnit) -> CtaBacktestUnitArea:
        """ CtaBacktestUnit => CtaBacktestUnitArea """
        kd = vnpy.tools.database_cta.DbCtaStatisticsData.setting.children().alias('children')
        model_select: peewee.ModelSelect = (
            vnpy.tools.database_cta.DbCtaStatisticsData.select(
                vnpy.tools.database_cta.DbCtaStatisticsData.strategy_name,
                vnpy.tools.database_cta.DbCtaStatisticsData.exchange,
                vnpy.tools.database_cta.DbCtaStatisticsData.symbol,
                vnpy.tools.database_cta.DbCtaStatisticsData.interval,
                vnpy.tools.database_cta.DbCtaStatisticsData.start,
                vnpy.tools.database_cta.DbCtaStatisticsData.end,
                kd.c.key.alias('setting_key'),
                peewee.fn.JSON_GROUP_ARRAY(peewee.fn.DISTINCT(kd.c.value)).python_value(json.loads).alias('setting_values')
            ).from_(vnpy.tools.database_cta.DbCtaStatisticsData, kd).where(
                True
                & (vnpy.tools.database_cta.DbCtaStatisticsData.strategy_name == unit.strategy_name)
                & (vnpy.tools.database_cta.DbCtaStatisticsData.exchange == unit.exchange.value)
                & (vnpy.tools.database_cta.DbCtaStatisticsData.symbol == unit.symbol)
                & (vnpy.tools.database_cta.DbCtaStatisticsData.interval == unit.interval.value)
                & (vnpy.tools.database_cta.DbCtaStatisticsData.start == unit.start)
                & (vnpy.tools.database_cta.DbCtaStatisticsData.end == unit.end)
            ).group_by(
                vnpy.tools.database_cta.DbCtaStatisticsData.strategy_name,
                vnpy.tools.database_cta.DbCtaStatisticsData.exchange,
                vnpy.tools.database_cta.DbCtaStatisticsData.symbol,
                vnpy.tools.database_cta.DbCtaStatisticsData.interval,
                vnpy.tools.database_cta.DbCtaStatisticsData.start,
                vnpy.tools.database_cta.DbCtaStatisticsData.end,
                kd.c.key
            )
        )

        setting_area: Dict[str, list] = {}

        for db_line in model_select:
            key: str = db_line.setting_key
            v_s: list = db_line.setting_values
            setting_area[key] = sorted(v_s)

        unit_area: CtaBacktestUnitArea = CtaBacktestUnitArea(
            strategy_name=unit.strategy_name,
            exchange=unit.exchange,
            symbol=unit.symbol,
            interval=unit.interval,
            start=unit.start,
            end=unit.end,
            setting_area=setting_area,
        )

        return unit_area

    @classmethod
    def query_data_list(cls, unit: CtaBacktestUnit, setting_filter: Optional[dict] = None) -> List[vnpy.tools.database_cta.CtaStatisticsData]:
        """"""
        # 筛选 vnpy.tools.database_cta.DbCtaStatisticsData.setting 这个 JSONField 字段,
        wwwww = True
        if setting_filter:
            for kkk, vvv in setting_filter.items():
                wwwww = wwwww & (vnpy.tools.database_cta.DbCtaStatisticsData.setting[kkk] == vvv)

        model_select: peewee.ModelSelect = (
            vnpy.tools.database_cta.DbCtaStatisticsData.select()
            .where(
                True
                & (vnpy.tools.database_cta.DbCtaStatisticsData.strategy_name == unit.strategy_name)
                & (vnpy.tools.database_cta.DbCtaStatisticsData.exchange == unit.exchange.value)
                & (vnpy.tools.database_cta.DbCtaStatisticsData.symbol == unit.symbol)
                & (vnpy.tools.database_cta.DbCtaStatisticsData.interval == unit.interval.value)
                & (vnpy.tools.database_cta.DbCtaStatisticsData.start == unit.start)
                & (vnpy.tools.database_cta.DbCtaStatisticsData.end == unit.end)
                & wwwww  # vnpy.tools.database_cta.DbCtaStatisticsData.setting
            )
        )

        cta_backtest_data_s: List[vnpy.tools.database_cta.CtaStatisticsData] = []

        for db_line in model_select:
            db_line: vnpy.tools.database_cta.DbCtaStatisticsData = db_line
            py_line: vnpy.tools.database_cta.CtaStatisticsData = vnpy.tools.database_cta.CtaStatisticsData.db2py(db_line=db_line)
            cta_backtest_data_s.append(py_line)

        return cta_backtest_data_s

    @classmethod
    def xx2_tmp_1(cls, unit: CtaBacktestUnit) -> CtaBacktestUnitArea:
        """"""
        if 0:
            mapping = {}

            model_select: peewee.ModelSelect = (
                vnpy.tools.database_cta.DbCtaStatisticsData.select(
                    vnpy.tools.database_cta.DbCtaStatisticsData.strategy_name,
                    vnpy.tools.database_cta.DbCtaStatisticsData.exchange,
                    vnpy.tools.database_cta.DbCtaStatisticsData.symbol,
                    vnpy.tools.database_cta.DbCtaStatisticsData.interval,
                    vnpy.tools.database_cta.DbCtaStatisticsData.start,
                    vnpy.tools.database_cta.DbCtaStatisticsData.end,
                    peewee.fn.DISTINCT(peewee.fn.JSON_EASH(vnpy.tools.database_cta.DbCtaStatisticsData.setting).key).alias("setting_key"),
                ).where(
                    True
                    & (vnpy.tools.database_cta.DbCtaStatisticsData.strategy_name == mapping["strategy_name"])
                    & (vnpy.tools.database_cta.DbCtaStatisticsData.exchange == mapping["exchange"].value)
                    & (vnpy.tools.database_cta.DbCtaStatisticsData.symbol == mapping["symbol"].symbol)
                    & (vnpy.tools.database_cta.DbCtaStatisticsData.interval == mapping["interval"].value)
                    & (vnpy.tools.database_cta.DbCtaStatisticsData.start == mapping["start"].start)
                    & (vnpy.tools.database_cta.DbCtaStatisticsData.end == mapping["end"].end)
                )
            )
            for db_line in model_select:
                db_line: vnpy.tools.database_cta.DbCtaStatisticsData = db_line
        #

    @classmethod
    def xx2_tmp_2(cls, unit: CtaBacktestUnit):
        """"""
        # https://github.com/coleifer/peewee/issues/767
        # https://docs.peewee-orm.com/en/latest/peewee/sqlite_ext.html#JSONField 然后搜索 see the json1 extension documentation.
        # https://github.com/coleifer/peewee/issues/2025
        # https://buildmedia.readthedocs.org/media/pdf/peewee/3.0a/peewee.pdf
        # https://docs.peewee-orm.com/en/latest/peewee/api.html#fn
        # 从上面的链接中可以看到一句话: The fn() helper is actually an instance of Function that implements a __getattr__ hook to provide a nice API for calling SQL functions.
        #
        # https://sqlite.org/json1.html
        #
        # https://docs.peewee-orm.com/en/latest/peewee/sqlite_ext.html#JSONField.children
        kd = vnpy.tools.database_cta.DbCtaStatisticsData.setting.children().alias('children')
        model_select: playhouse.sqlite_ext.ModelSelect = (
            vnpy.tools.database_cta.DbCtaStatisticsData.select(
                playhouse.sqlite_ext.fn.DISTINCT(kd.c.key).alias('setting_field')
            ).from_(vnpy.tools.database_cta.DbCtaStatisticsData, kd).where(
                True
                & (vnpy.tools.database_cta.DbCtaStatisticsData.strategy_name == "DoubleMaStrategy")
                & (vnpy.tools.database_cta.DbCtaStatisticsData.exchange == Exchange.CZCE.value)
                & (vnpy.tools.database_cta.DbCtaStatisticsData.symbol == "MAL8")
                & (vnpy.tools.database_cta.DbCtaStatisticsData.interval == Interval.MINUTE.value)
                & (vnpy.tools.database_cta.DbCtaStatisticsData.start == datetime.datetime(year=2023, month=10, day=23, hour=0, minute=0, second=0))
                & (vnpy.tools.database_cta.DbCtaStatisticsData.end == datetime.datetime(year=2023, month=10, day=29, hour=23, minute=59, second=59))
            )
        )
        fields: List[str] = []
        for db_line in model_select:
            fields.append(db_line.setting_field)

        # 求字段的最大最小值,
        ssssss_list = []
        for field in fields:
            ssssss_list.append(playhouse.sqlite_ext.fn.MIN(vnpy.tools.database_cta.DbCtaStatisticsData.setting[field]).alias(field + '_MIN'))
            ssssss_list.append(playhouse.sqlite_ext.fn.MAX(vnpy.tools.database_cta.DbCtaStatisticsData.setting[field]).alias(field + '_MAX'))
        #
        model_select: playhouse.sqlite_ext.ModelSelect = (
            vnpy.tools.database_cta.DbCtaStatisticsData.select(
                *ssssss_list
            ).where(
                True
                & (vnpy.tools.database_cta.DbCtaStatisticsData.strategy_name == "DoubleMaStrategy")
                & (vnpy.tools.database_cta.DbCtaStatisticsData.exchange == Exchange.CZCE.value)
                & (vnpy.tools.database_cta.DbCtaStatisticsData.symbol == "MAL8")
                & (vnpy.tools.database_cta.DbCtaStatisticsData.interval == Interval.MINUTE.value)
                & (vnpy.tools.database_cta.DbCtaStatisticsData.start == datetime.datetime(year=2023, month=10, day=23, hour=0, minute=0, second=0))
                & (vnpy.tools.database_cta.DbCtaStatisticsData.end == datetime.datetime(year=2023, month=10, day=29, hour=23, minute=59, second=59))
            )
        )

        for db_line in model_select:
            db_line = db_line


class EchartsWidget(PySide6.QtWidgets.QWidget):
    """"""

    def __init__(self, parent: Optional[PySide6.QtWidgets.QWidget] = None) -> None:
        super().__init__(parent)

        self.init_ui()

    def init_ui(self) -> None:
        """"""
        mainLayout: QtWidgets.QVBoxLayout = QtWidgets.QVBoxLayout(self)

        self.webEngineView: PySide6.QtWebEngineWidgets.QWebEngineView = PySide6.QtWebEngineWidgets.QWebEngineView(self)

        html_file: str = pathlib.Path(__file__).parent.joinpath("show.html").as_posix()
        self.webEngineView.load(PySide6.QtCore.QUrl(f"file:///{html_file}"))

        mainLayout.addWidget(self.webEngineView)

    def setValue(self) -> None:
        """"""
        sender: PySide6.QtCore.QObject = self.sender()
        assert isinstance(sender, UnitAreaWidget)
        if not sender.simple_check_ok():
            return

        output(f"重新绘图, 载入开始,")
        dttm_beg: datetime.datetime = datetime.datetime.now()
        xyza_s: List[list] = sender.get_data_for_draw()
        dttm_end: datetime.datetime = datetime.datetime.now()
        output(f"重新绘图, 载入结束, 耗时{dttm_end - dttm_beg}, 数据有{len(xyza_s)-1}条")

        type_name: str = sender.get_name_for_draw()

        head_body: List[list] = xyza_s

        head_data: list = head_body[0]
        body_data: list = head_body[1:]
        body_data.sort(key=lambda _: _[0])  # 用x排序

        js: str = "setValue('{}',{},{})".format(type_name, head_data, body_data)
        self.webEngineView.page().runJavaScript(js)


class MainWindow(QtWidgets.QWidget):
    """
    Main window of the trading platform.
    """

    def __init__(self) -> None:
        """"""
        super().__init__()

        self.window_title: str = f"回测结果分析"

        self.init_ui()

    def init_ui(self) -> None:
        """"""
        self.setWindowTitle(self.window_title)

        mainLayout: QtWidgets.QHBoxLayout = QtWidgets.QHBoxLayout(self)  # 水平布局

        vboxLayout: QtWidgets.QVBoxLayout = QtWidgets.QVBoxLayout()

        unit_list_widget: UnitListWidget = UnitListWidget(self)
        vboxLayout.addWidget(unit_list_widget)

        horizontal_line_1: QtWidgets.QFrame = QtWidgets.QFrame()  # 水平分隔线
        horizontal_line_1.setFrameShape(QtWidgets.QFrame.HLine)
        horizontal_line_1.setFrameShadow(QtWidgets.QFrame.Plain)
        horizontal_line_1.setMaximumWidth(400)
        vboxLayout.addWidget(horizontal_line_1)

        tips_label: QtWidgets.QLabel = QtWidgets.QLabel(text="黑色参数用于筛选数据。红色参数用于绘图。")
        tips_label.setText("黑色<font style='color:blue'>参数用于筛选数据。</font><font style='color:red'>红色</font><font style='color:blue'>参数用于绘图。</font>")
        vboxLayout.addWidget(tips_label)

        horizontal_line_2: QtWidgets.QFrame = QtWidgets.QFrame()
        horizontal_line_2.setFrameShape(QtWidgets.QFrame.HLine)
        horizontal_line_2.setFrameShadow(QtWidgets.QFrame.Plain)
        horizontal_line_2.setMaximumWidth(400)
        vboxLayout.addWidget(horizontal_line_2)

        unit_area_widget: UnitAreaWidget = UnitAreaWidget(self)
        vboxLayout.addWidget(unit_area_widget)

        unit_list_widget.setArgs.connect(unit_area_widget.reload_data)

        mainLayout.addLayout(vboxLayout)

        horizontal_line_3: QtWidgets.QFrame = QtWidgets.QFrame()
        horizontal_line_3.setFrameShape(QtWidgets.QFrame.HLine)
        horizontal_line_3.setFrameShadow(QtWidgets.QFrame.Plain)
        horizontal_line_3.setMaximumWidth(400)
        vboxLayout.addWidget(horizontal_line_3)

        vboxLayout.addItem(
            QtWidgets.QSpacerItem(1, 1, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
        )

        echarts_widget: EchartsWidget = EchartsWidget(self)
        mainLayout.addWidget(echarts_widget)

        unit_area_widget.drawIt.connect(echarts_widget.setValue)


if __name__ == "__main__":
    if 0:
        import logging
        logging.basicConfig(level=logging.DEBUG)

    qapp = QtWidgets.QApplication.instance()
    if qapp is None:
        qapp: QtWidgets.QApplication = QtWidgets.QApplication(sys.argv)

    main_window = MainWindow()
    main_window.showMaximized()

    qapp.exec()
